﻿using System;
using System.Collections.Generic;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;
using System.Xml.Linq;
using System.Windows.Media.Imaging;

namespace MapEditor
{
    public class WorldManager
    {
        public int TerrainEditLayer=0;

        private int _terrainTileRadius;
        private int _terrainWidthInTiles;
        private int _terrainHeightInTiles;
        private Canvas _mapCanvas;
        private string _mapName;
        private bool _floodFilling = false;

        private SLGameEngine.ObjectTerrain[,] _terrainTiles;
        private List<SLGameEngine.ObjectMap> _mapObjects = new List<SLGameEngine.ObjectMap>();
        private List<SLGameEngine.ObjectGame> _gameObjects = new List<SLGameEngine.ObjectGame>();
        private List<SLGameEngine.ObjectCreature> _creatureObjects = new List<SLGameEngine.ObjectCreature>();

        public WorldManager(Canvas mapCanvas)
        {                
            _mapCanvas = mapCanvas;
        }

        public void CreateNewMap(int widthInTiles, int heightInTiles, int tileRadius, string mapName, int layerCount)
        {
            _mapCanvas.Children.Clear();

            _mapName = mapName;
            _terrainWidthInTiles = widthInTiles;
            _terrainHeightInTiles = heightInTiles;
            _terrainTileRadius = tileRadius;

            _terrainTiles = new SLGameEngine.ObjectTerrain[heightInTiles, widthInTiles];

            _mapCanvas.Width = _terrainWidthInTiles * _terrainTileRadius;
            _mapCanvas.Height = _terrainHeightInTiles * _terrainTileRadius;

            for (int y = 0; y < _terrainHeightInTiles; y++)
            {
                for (int x = 0; x < _terrainWidthInTiles; x++)
                {
                    SLGameEngine.ObjectTerrain terrainObj = new SLGameEngine.ObjectTerrain(_terrainTileRadius * x, _terrainTileRadius * y, _terrainTileRadius, layerCount, SLGameEngine.Consts.None);
                    _terrainTiles[y,x] = terrainObj;
                    _mapCanvas.Children.Add(terrainObj);
                }
            }
        }

        public SLGameEngine.ObjectTerrain AddTerrainObject(Point pt, SLGameEngine.ObjectTerrain terrainTemplate)
        {
            int tileX = (int)(pt.X / _terrainTileRadius);
            int tileY = (int)(pt.Y / _terrainTileRadius);

            SLGameEngine.ObjectTerrain terrainObj = _terrainTiles[tileY, tileX];
            Image img = terrainTemplate.GetLayerImage(0);
            int baseObjectID = terrainTemplate.GetLayerBaseObjectID(0);

            terrainObj.SetLayer(TerrainEditLayer, baseObjectID, ((BitmapImage)img.Source).UriSource.OriginalString);
            return terrainObj;
        } 

        public void ApplyOpacityMask(Point pt, int opacityID, Image opacityImage)
        {
            int tileX = (int)(pt.X / _terrainTileRadius);
            int tileY = (int)(pt.Y / _terrainTileRadius);

            SLGameEngine.ObjectTerrain terrainObj = _terrainTiles[tileY, tileX];
            terrainObj.ApplyOpacityMask(TerrainEditLayer, opacityID, opacityImage);
        }

        public void DeleteObject(SLGameEngine.ObjectBase obj)
        {
            if (_mapCanvas.Children.Contains(obj) && obj.GetType() != typeof(SLGameEngine.ObjectTerrain))
                _mapCanvas.Children.Remove(obj);

            if (obj.GetType() == typeof(SLGameEngine.ObjectCreature) && _creatureObjects.Contains((SLGameEngine.ObjectCreature)obj))
                _creatureObjects.Remove((SLGameEngine.ObjectCreature)obj);
            else if (obj.GetType() == typeof(SLGameEngine.ObjectMap) && _mapObjects.Contains((SLGameEngine.ObjectMap)obj))
                _mapObjects.Remove((SLGameEngine.ObjectMap)obj);
            else if (obj.GetType() == typeof(SLGameEngine.ObjectGame) && _gameObjects.Contains((SLGameEngine.ObjectGame)obj))
                _gameObjects.Remove((SLGameEngine.ObjectGame)obj);
            else if (obj.GetType() == typeof(SLGameEngine.ObjectTerrain))
                ((SLGameEngine.ObjectTerrain)obj).ClearLayers();           
        }

        public SLGameEngine.ObjectBase DrawObject(double x, double y, SLGameEngine.ObjectBase obj)
        {
            if (obj.GetType() == typeof(SLGameEngine.ObjectGame))
                return AddGameObject(obj, x, y);
            else if (obj.GetType() == typeof(SLGameEngine.ObjectCreature))
                return AddCreatureObject(obj, x, y);
            else if (obj.GetType() == typeof(SLGameEngine.ObjectMap))
                return AddMapObject(obj, x, y);
            
            return null;
        }

        private SLGameEngine.ObjectGame AddGameObject(SLGameEngine.ObjectBase obj, double x, double y)
        {
            SLGameEngine.ObjectGame gameObj = (SLGameEngine.ObjectGame)((SLGameEngine.ObjectGame)obj).Clone(0);
            gameObj.SetValue(Canvas.LeftProperty, x);
            gameObj.SetValue(Canvas.TopProperty, y);

            _gameObjects.Add((SLGameEngine.ObjectGame)gameObj);
            _mapCanvas.Children.Add(gameObj);
            
            return gameObj;
        }
        private SLGameEngine.ObjectCreature AddCreatureObject(SLGameEngine.ObjectBase obj, double x, double y)
        {
            SLGameEngine.ObjectCreature creatureObj = (SLGameEngine.ObjectCreature)((SLGameEngine.ObjectCreature)obj).Clone(0);
            creatureObj.SetValue(Canvas.LeftProperty, x);
            creatureObj.SetValue(Canvas.TopProperty, y);
            creatureObj.Animate(SLGameEngine.Enums.Direction.South, SLGameEngine.Enums.AnimationType.Walk, false);
            _creatureObjects.Add((SLGameEngine.ObjectCreature)creatureObj);
            _mapCanvas.Children.Add(creatureObj);

            return creatureObj;
        }

        private SLGameEngine.ObjectMap AddMapObject(SLGameEngine.ObjectBase obj, double x, double y)
        {
            SLGameEngine.ObjectMap mapObject = (SLGameEngine.ObjectMap)((SLGameEngine.ObjectMap)obj).Clone(0);
            mapObject.SetValue(Canvas.LeftProperty, x);
            mapObject.SetValue(Canvas.TopProperty, y);

            _mapObjects.Add((SLGameEngine.ObjectMap)mapObject);
            _mapCanvas.Children.Add(mapObject);

            return mapObject;
        }

        public void Fill(double x, double y, SLGameEngine.ObjectTerrain terrainObject)
        {
            if (false == _floodFilling)
            {
                _floodFilling = true;
                int tileX = (int)(x / _terrainTileRadius);
                int tileY = (int)(y / _terrainTileRadius);

                SLGameEngine.ObjectTerrain targetTerrain = _terrainTiles[tileY, tileX];
                Image targetImg = targetTerrain.GetLayerImage(TerrainEditLayer);
                string targetFileName = SLGameEngine.Utils.GetImageFileName(targetImg);
                targetTerrain.BaseObjectID = terrainObject.BaseObjectID;
                Image srcImg = terrainObject.GetLayerImage(0);
                string srcFileName = SLGameEngine.Utils.GetImageFileName(srcImg);

                FloodFill(tileX, tileY, targetFileName, srcFileName, terrainObject.BaseObjectID);
                _floodFilling = false;
            }
        }


        public string Dropper(Point pt)
        {
            int tileX = (int)(pt.X / _terrainTileRadius);
            int tileY = (int)(pt.Y / _terrainTileRadius);

            SLGameEngine.ObjectTerrain terrainObj = _terrainTiles[tileY, tileX];

            return SLGameEngine.Utils.GetImageFileName(terrainObj.GetLayerImage(TerrainEditLayer));
        }

        private void FloodFill(int x, int y, string targetFileName, string srcFileName, int baseObjectID)
        {
            SLGameEngine.ObjectTerrain currentTerrain = _terrainTiles[y, x];
            Image img = currentTerrain.GetLayerImage(TerrainEditLayer);
            //int baseObjectID = currentTerrain.GetLayerBaseObjectID(TerrainEditLayer);
            string fileName = SLGameEngine.Utils.GetImageFileName(img);

            if (fileName != targetFileName)
                return;
            if (fileName == srcFileName)
                return;
            
            currentTerrain.SetLayer(TerrainEditLayer, baseObjectID, srcFileName);

            if (x - 1 >= 0)
                FloodFill(x - 1, y, targetFileName, srcFileName, baseObjectID);

            if (x + 1 < _terrainWidthInTiles)
                FloodFill(x + 1, y, targetFileName, srcFileName, baseObjectID);

            if (y - 1 >= 0)
                FloodFill(x, y - 1, targetFileName, srcFileName, baseObjectID);

            if (y + 1 < _terrainHeightInTiles)
                FloodFill(x, y + 1, targetFileName, srcFileName, baseObjectID);
        }
        
        public void ToggleGrid()
        {
            SLGameEngine.Consts.ShowGrid = !SLGameEngine.Consts.ShowGrid;
            for (int y = 0; y < _terrainHeightInTiles; y++)
            {
                for (int x = 0; x < _terrainWidthInTiles; x++)
                {
                    SLGameEngine.ObjectTerrain tile = _terrainTiles[y, x];
                    tile.Deselect();
                }
            }
        }

        public void SaveMap()
        {
            SaveFileDialog sfd = new SaveFileDialog();
            sfd.Filter = "map files (*.xml)|*.xml|All files (*.*)|*.*";
            sfd.ShowDialog();
            System.IO.Stream stream = sfd.OpenFile();
            if (null != stream)
            {

                XDocument document = new XDocument();

                document.Declaration = new XDeclaration("1.0", "utf-8", "true");

                XElement rootElement = new XElement(SLGameEngine.Consts.MapAttributeMap);
                document.Add(rootElement);

                XElement childElement = new XElement(SLGameEngine.Consts.MapAttributeMapName);
                childElement.Value = _mapName;
                rootElement.Add(childElement);

                childElement = new XElement(SLGameEngine.Consts.MapAttributeWidthInTiles);
                childElement.Value = Convert.ToString(_terrainWidthInTiles);
                rootElement.Add(childElement);

                childElement = new XElement(SLGameEngine.Consts.MapAttributeHeightInTiles);
                childElement.Value = Convert.ToString(_terrainHeightInTiles);
                rootElement.Add(childElement);

                childElement = new XElement(SLGameEngine.Consts.MapAttributeTileRadius);
                childElement.Value = Convert.ToString(_terrainTileRadius);
                rootElement.Add(childElement);

                childElement = new XElement(SLGameEngine.Consts.MapAttributeLayerCount);
                childElement.Value = Convert.ToString(SLGameEngine.Consts.TerrainLayerCount);
                rootElement.Add(childElement);

                foreach (SLGameEngine.ObjectMap mapObject in _mapObjects)
                {
                    childElement = new XElement(SLGameEngine.Consts.MapAttributeMapObject);
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributeBaseObjectID, mapObject.BaseObjectID);
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributePosX, Convert.ToString(mapObject.GetValue(Canvas.LeftProperty)));
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributePosY, Convert.ToString(mapObject.GetValue(Canvas.TopProperty)));
                    rootElement.Add(childElement);
                }

                foreach (SLGameEngine.ObjectGame gameObject in _gameObjects)
                {
                    childElement = new XElement(SLGameEngine.Consts.MapAttributeGameObject);
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributeBaseObjectID, gameObject.BaseObjectID);
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributePosX, Convert.ToString(gameObject.GetValue(Canvas.LeftProperty)));
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributePosY, Convert.ToString(gameObject.GetValue(Canvas.TopProperty)));
                    rootElement.Add(childElement);
                }
                foreach (SLGameEngine.ObjectCreature creatureObject in _creatureObjects)
                {
                    childElement = new XElement(SLGameEngine.Consts.MapAttributeCreatureObject);
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributeBaseObjectID, creatureObject.BaseObjectID);
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributePosX, Convert.ToString(creatureObject.GetValue(Canvas.LeftProperty)));
                    childElement.SetAttributeValue(SLGameEngine.Consts.MapAttributePosY, Convert.ToString(creatureObject.GetValue(Canvas.TopProperty)));
                    rootElement.Add(childElement);
                }

                for (int i = 0; i < SLGameEngine.Consts.TerrainLayerCount; i++)
                {
                    childElement = new XElement(SLGameEngine.Consts.MapAttributeTileIDsLayer + i.ToString());

                    string tileIDs = String.Empty;
                    for (int y = 0; y < _terrainHeightInTiles; y++)
                    {
                        for (int x = 0; x < _terrainWidthInTiles; x++)
                        {
                            int ID = _terrainTiles[y, x].GetLayerBaseObjectID(i);
                            tileIDs += Convert.ToString(ID) + ",";
                        }
                    }
                    childElement.Value = tileIDs;
                    rootElement.Add(childElement);
                }

                for (int i = 0; i < SLGameEngine.Consts.TerrainLayerCount; i++)
                {
                    childElement = new XElement(SLGameEngine.Consts.MapAttributeOpacityIDsLayer + i.ToString());

                    string opacityIDs = String.Empty;
                    for (int y = 0; y < _terrainHeightInTiles; y++)
                    {
                        for (int x = 0; x < _terrainWidthInTiles; x++)
                        {
                            int ID = _terrainTiles[y, x].GetLayerOpacityID(i);
                            opacityIDs += Convert.ToString(ID) + ",";
                        }
                    }
                    childElement.Value = opacityIDs;
                    rootElement.Add(childElement);
                }

                document.Save(stream);

                stream.Flush();
                stream.Close();
            }
        }

        private void ShowLayer(SLGameEngine.Enums.Layer layer, bool show)
        {
            //if (layer != Enums.Layer.GameObjectLayer)
            //{
            //    for (int y = 0; y < _heightInTiles; y++)
            //    {
            //        for (int x = 0; x < _widthInTiles; x++)
            //        {
            //            TerrainTile tile = _mapTiles[y, x];
            //            tile.ShowLayer(layer, show);
            //        }
            //    }
            //}
            //else
            //{
            //    for (int x = 0; x < _gameObjects.Count; x++)
            //    {
            //        GameObject gameObj = (GameObject)_gameObjects[x];
            //        if (true == show)
            //            gameObj.Visibility = Visibility.Visible;
            //        else
            //            gameObj.Visibility = Visibility.Collapsed;
            //    }
            //}
        }

        public bool ToggleLayer(SLGameEngine.Enums.Layer layer)
        {
            //switch (layer)
            //{
            //    case Enums.Layer.GameObjectLayer:
            //        _viewLayerGameObjects = !_viewLayerGameObjects;
            //        ShowLayer(layer, _viewLayerGameObjects);
            //        return _viewLayerGameObjects;
            //    case Enums.Layer.Layer1:
            //        _viewLayer1 = !_viewLayer1;
            //        ShowLayer(layer, _viewLayer1);
            //        return _viewLayer1;
            //    case Enums.Layer.Layer2:
            //        _viewLayer2 = !_viewLayer2;
            //        ShowLayer(layer, _viewLayer2);
            //        return _viewLayer2;
            //}
            return false;
        }

        public void LoadMap()
        {
            string mapName      = String.Empty;
            int widthInTiles    = 0;
            int heightInTiles   = 0;
            int tileRadius      = 0;
            int layerCount = 0;

            OpenFileDialog opf = new OpenFileDialog();
            opf.Filter = "map files (*.xml)|*.xml|All files (*.*)|*.*";
            opf.ShowDialog();
            
            System.IO.Stream stream = opf.File.OpenRead();

            if (null != stream)
            {
                XDocument document = XDocument.Load(stream);

                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeMapName))
                    mapName = element.Value;
                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeWidthInTiles))
                    widthInTiles = Convert.ToInt32(element.Value);
                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeHeightInTiles))
                    heightInTiles = Convert.ToInt32(element.Value);
                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeTileRadius))
                    tileRadius = Convert.ToInt32(element.Value);
                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeLayerCount))
                    layerCount = Convert.ToInt32(element.Value);

                CreateNewMap(widthInTiles, heightInTiles, tileRadius, mapName, layerCount);

                for (int layer = 0; layer < layerCount; layer++)
                {
                    string layerAttribute = SLGameEngine.Consts.MapAttributeTileIDsLayer + layer.ToString();
                    string tileIDs = String.Empty;

                    foreach (XElement element in document.Descendants(layerAttribute))
                        tileIDs = element.Value;

                    string[] ids = tileIDs.Split(',');

                    for (int y = 0; y < _terrainHeightInTiles; y++)
                    {
                        for (int x = 0; x < _terrainWidthInTiles; x++)
                        {
                            int baseObjID = 0;
                            int index = (y * _terrainWidthInTiles) + x;
                            if (index < ids.Length)
                                baseObjID = Convert.ToInt32(ids[index]);
                            if (baseObjID != -1)
                            {
                                SLGameEngine.ObjectTerrain terrainObj = (SLGameEngine.ObjectTerrain)ObjectTemplateManager.GetBaseObject(baseObjID);
                                if (null != terrainObj)
                                {
                                    Image img = terrainObj.GetLayerImage(layer);
                                    _terrainTiles[y, x].SetLayer(layer, baseObjID, SLGameEngine.Utils.GetImageFileName(img));
                                }
                            }
                        }
                    }
                }

                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeMapObject))
                {
                    int baseObjID = (int)element.Attribute(SLGameEngine.Consts.MapAttributeBaseObjectID);
                    int posX = (int)element.Attribute(SLGameEngine.Consts.MapAttributePosX);
                    int posY = (int)element.Attribute(SLGameEngine.Consts.MapAttributePosY);

                    SLGameEngine.ObjectMap mapObject = (SLGameEngine.ObjectMap)ObjectTemplateManager.GetBaseObject(baseObjID);
                    AddMapObject(mapObject, posX, posY);
                }

                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeGameObject))
                {
                    int baseObjID = (int)element.Attribute(SLGameEngine.Consts.MapAttributeBaseObjectID);
                    int posX = (int)element.Attribute(SLGameEngine.Consts.MapAttributePosX);
                    int posY = (int)element.Attribute(SLGameEngine.Consts.MapAttributePosY);

                    SLGameEngine.ObjectGame gameObj = (SLGameEngine.ObjectGame)ObjectTemplateManager.GetBaseObject(baseObjID);
                    AddGameObject(gameObj, posX, posY);
                }

                foreach (XElement element in document.Descendants(SLGameEngine.Consts.MapAttributeCreatureObject))
                {
                    int baseObjID = (int)element.Attribute(SLGameEngine.Consts.MapAttributeBaseObjectID);
                    int posX = (int)element.Attribute(SLGameEngine.Consts.MapAttributePosX);
                    int posY = (int)element.Attribute(SLGameEngine.Consts.MapAttributePosY);

                    SLGameEngine.ObjectCreature gameObj = (SLGameEngine.ObjectCreature)ObjectTemplateManager.GetBaseObject(baseObjID);
                    AddCreatureObject(gameObj, posX, posY);
                }
                stream.Close();
            }
        }

        public SLGameEngine.ObjectBase SelectObject(Point pt, ref Point selectionPoint)
        {
            for (int i = 0; i < _creatureObjects.Count; i++)
            {
                if (true == _creatureObjects[i].DetectCollision(pt, ref selectionPoint))
                    return _creatureObjects[i];
            }
            for (int i = 0; i < _gameObjects.Count; i++)
            {
                if (true == _gameObjects[i].DetectCollision(pt, ref selectionPoint))
                    return _gameObjects[i];
            }

            for (int i = 0; i < _mapObjects.Count; i++)
            {
                if (true == _mapObjects[i].DetectCollision(pt, ref selectionPoint))
                    return _mapObjects[i];
            }

            int tileX = (int)(pt.X / _terrainTileRadius);
            int tileY = (int)(pt.Y / _terrainTileRadius);

            return _terrainTiles[tileY, tileX];
        }
    }
}
